#include "userprog/syscall.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
#include "threads/synch.h"
#include "threads/vaddr.h"
#include "threads/init.h"
#include "filesys/filesys.h"
#include "filesys/file.h"
#include "userprog/process.h"
#include "devices/input.h"

#ifdef VM
#include "vm/mmap.h"
#endif

#ifdef FILESYS
#include "filesys/cache.h"
#include "filesys/inode.h"
#include "filesys/directory.h"
#endif

static void syscall_handler (struct intr_frame *);
static int get_user (uint8_t *uaddr);
static bool put_user (uint8_t *udst, uint8_t byte);
void check_uaddr(uint8_t *uaddr);
static void check_string_read(char **uaddr, int len);
static void check_string_write(char **uaddr, int len);

// both syscall_init and file_init are called from 'main' in threads/init.c
void
syscall_init (void) 
{
	lock_init(&file_lock);
  intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
}

static void
syscall_handler (struct intr_frame *f) 
{
	check_uaddr( f->esp );
	int syscall_num = *(int*)(f->esp);
	struct thread *cur = thread_current();
	// for check the cases that cause page fault
	cur->syscall = true;

	// file is defined at file.h
	struct file *sys_file;
	int fd;
	char* buf;
	int size;

#ifdef FILESYS
	struct inode *temp_inode;
	struct dir *dir;
#endif

//	printf("%d %x %x\n",syscall_num,(unsigned int)(f->esp), (unsigned int)PHYS_BASE);

	switch(syscall_num){
		//0  void halt (void)
		case SYS_HALT:                   /* Halt the operating system. */
			power_off();	
			break;


		//1 void exit (int status)
		case SYS_EXIT:                   /* Terminate this process. */
			check_uaddr( f->esp+4 );
			syscall_exit( *(int*)(f->esp+4)  );	// pass exit status value
			break;


		//2 pid_t exec (const char *cmd_line)
		case SYS_EXEC:                   /* Start another process. */
			check_uaddr( f->esp+4 );
			if( *(char**)(f->esp+4) == NULL){
					syscall_exit(-1);
			}
			check_string_read( (char**)(f->esp+4), 15);
			f->eax = process_execute( *(char**)(f->esp+4) );	// pass the file name
			break;

		//3 int wait (pid_t pid)
		case SYS_WAIT:                   /* Wait for a child process to die. */
			check_uaddr( f->esp+4 );
	
			f->eax = process_wait( *(int*)(f->esp+4) );
			break;


		//4 bool create (const char *file, unsigned initial_size)
		case SYS_CREATE:                 /* Create a file. */
			// bool filesys_create (const char *name, off_t initial_size);			
			check_uaddr( f->esp+8 );
			if( *(char**)(f->esp+4) == NULL) syscall_exit(-1);
			if( *(off_t*)(f->esp+8) < 0) syscall_exit(-1);
//			printf("%s\n", *(char**)(f->esp+4), 15);
			check_string_read( (char**)(f->esp+4), 15);

//			lock_acquire(&file_lock);
			f->eax = filesys_create( *(char**)(f->esp+4), *(off_t*)(f->esp+8), false );
//			lock_release(&file_lock);
			break;


		//5 bool remove (const char *file)
		case SYS_REMOVE:                 /* Delete a file. */
			// bool filesys_remove (const char *name);
			check_uaddr( f->esp+4 );
			if( *(char**)(f->esp+4) == NULL) syscall_exit(-1);
			check_string_read( (char**)(f->esp+4), 15);

			f->eax = filesys_remove( *(char**)(f->esp+4) );
			break;


		//6 int open (const char *file)
		case SYS_OPEN:                   /* Open a file. */
			// struct file *filesys_open (const char *name);
			check_uaddr( f->esp+4 );
			if( *(char**)(f->esp+4) == NULL){
					syscall_exit(-1);
			}
			check_string_read( (char**)(f->esp+4), 15);

//			lock_acquire(&file_lock);
			sys_file = (struct file*)filesys_open( *(char**)(f->esp+4) );
			if(sys_file == NULL){
				f->eax = -1;
				break;
			}
			sys_file->fd = fd_count++;		// fd_cnt is at filesys/file.h
			list_push_back( &cur->file_list, &sys_file->thread_file_elem);
			
			f->eax = sys_file->fd;
			break;


		//7 int filesize (int fd)
		case SYS_FILESIZE:               /* Obtain a file's size. */
			check_uaddr( f->esp+4 );
			if( *(int*)(f->esp+4) < 3 ) syscall_exit(-1);
			
			sys_file = get_file_from_thread( *(int*)(f->esp+4) );
			if(sys_file == NULL){
				syscall_exit(-1);
			}
		
			f->eax = file_length( sys_file );
			break;


		//8 int read (int fd, void *buffer, unsigned size)
		case SYS_READ:                   /* Read from a file. */
			check_uaddr( f->esp+12 );
			if( *(char**)(f->esp+8) == NULL) syscall_exit(-1);
			if( *(int*)(f->esp+12) < 0 ) syscall_exit(-1);
			check_string_write( (char**)(f->esp+8), *(int*)(f->esp+12) );

			lock_acquire(&file_lock);
			fd = *(int*)(f->esp+4);
			size = *(int*)(f->esp+12);
			buf = *(char**)(f->esp+8);	
			if(fd == 0){
				int temp = 0;
				uint8_t c;
				while(temp < size){
					c = input_getc();
					if(c == '\n'){
						buf[temp++]='\r';
						break;
					}
					buf[temp++]=c;
				}
				f->eax = temp;
			}
			else if(fd == 1 || fd == 2){
				f->eax = -1;
			}
			else{
				sys_file = get_file_from_thread( fd );
				if( sys_file == NULL){
					lock_release(&file_lock);
					syscall_exit(-1);
				}

				f->eax = file_read( sys_file, buf, size); 
			}
		
			lock_release(&file_lock);
			break;


		//9 int write (int fd, const void *buffer, unsigned size)
		case SYS_WRITE:                  /* Write to a file. */
			// off_t file_write (struct file *, const void *, off_t);
			check_uaddr( f->esp+12 );
			if( *(char**)(f->esp+8) == NULL) syscall_exit(-1);
			if( *(int*)(f->esp+12) < 0 ) syscall_exit(-1);
			check_string_read( (char**)(f->esp+8), *(int*)(f->esp+12) );

			lock_acquire(&file_lock);
			fd = *(int*)(f->esp+4);
			size = *(int*)(f->esp+12);
			buf = *(char**)(f->esp+8);	

			if(fd == 1){
				putbuf(buf, size);
				f->eax = size;
			}
			else if(fd == 0 || fd == 2){
				f->eax = -1;
			}
			else{
				sys_file = get_file_from_thread( fd );
				if( sys_file == NULL){
//					f->eax = -1	;
					lock_release(&file_lock);
					syscall_exit(-1);
					break;
				}
				f->eax = file_write( sys_file, buf, size); 
			}

			lock_release(&file_lock);
			break;


		//10 void seek (int fd, unsigned position)
		case SYS_SEEK:                   /* Change position in a file. */
			// void file_seek (struct file *, off_t);
			check_uaddr( f->esp+8 );
			if( *(int*)(f->esp+4) < 3 ) syscall_exit(-1);

//			lock_acquire(&file_lock);
			sys_file = get_file_from_thread( *(int*)(f->esp+4) );
			if(sys_file == NULL){
//				lock_release(&file_lock);
				syscall_exit(-1);
			}
			
			file_seek( sys_file, *(off_t*)(f->esp+8) );
//			lock_release(&file_lock);
			break;


		//11 unsigned tell (int fd)
		case SYS_TELL:                   /* Report current position in a file. */
			// off_t file_tell (struct file *);
			check_uaddr( f->esp+4 );
			if( *(int*)(f->esp+4) < 3 ) syscall_exit(-1);

			sys_file = get_file_from_thread( *(int*)(f->esp+4) );
			if(sys_file == NULL){
				syscall_exit(-1);
			}
			
			f->eax = file_tell( sys_file );
			break;


		//12 void close (int fd)
		case SYS_CLOSE:                  /* Close a file. */
			// void file_close (struct file *);
			check_uaddr( f->esp+4 );
			if( *(int*)(f->esp+4) < 3 ) syscall_exit(-1);

			sys_file = get_file_from_thread( *(int*)(f->esp+4) );
			if(sys_file == NULL){
				syscall_exit(-1);
			}
#ifdef VM

			sema_down(&mmap_sema);

			struct mmap checker;
			struct hash_elem *m_elem;
			struct mmap *_m;

			checker.mmap_id = *(int*)(f->esp+4);
			m_elem = hash_find (cur->mmaps, &checker.hash_elem);
				
			if(m_elem != NULL){
				_m = hash_entry(m_elem, struct mmap, hash_elem);
				cur->syscall = false;
				sema_up(&mmap_sema);
				break;
			}

			sema_up(&mmap_sema);

#endif
			list_remove( &sys_file->thread_file_elem);
			file_close(sys_file);

//			lock_release(&file_lock);
			break;

#ifdef VM
		// mapid_t mmap(int fd, void *addr)
		case SYS_MMAP:
			check_uaddr( f->esp+8 );
			sema_down(&mmap_sema);
			f->eax = syscall_mmap( *(int*)(f->esp+4), *(void**)(f->esp+8) );
			sema_up(&mmap_sema);
			break;

		// void munmap(mapid_t mapping)
		case SYS_MUNMAP:
			check_uaddr( f->esp+4 );
			sema_down(&mmap_sema);

			if(! syscall_munmap( *(int*)(f->esp+4) )){
				sema_up(&mmap_sema);
				syscall_exit(-1);
				break;
			}

			sema_up(&mmap_sema);
			break;
#endif
#ifdef FILESYS
		// bool chdir(const char *dir)
		case SYS_CHDIR:
			check_uaddr( f->esp+4 );
			if( *(char**)(f->esp+4) == NULL) syscall_exit(-1);
			check_string_read( (char**)(f->esp+4), 16 );

			f->eax = dir_chdir( *(char**)(f->esp+4) );
			break;

		// bool mkdir(const char *dir)
		case SYS_MKDIR:
			check_uaddr( f->esp+4 );
			if( *(char**)(f->esp+4) == NULL) syscall_exit(-1);
			check_string_read( (char**)(f->esp+4), 16 );

			f->eax = filesys_create( *(char**)(f->esp+4), 0, true);
			break;

		// bool readdir(int fd, const char *dir)
		case SYS_READDIR:
			check_uaddr( f->esp+8 );
			if( *(char**)(f->esp+8) == NULL) syscall_exit(-1);
			check_string_read( (char**)(f->esp+8), 16 );
			
			sys_file = get_file_from_thread( *(int*)(f->esp+4) );
			if(sys_file == NULL){
				f->eax = false;
				break;
			}

			temp_inode= file_get_inode( sys_file);
			if(!get_is_dir(temp_inode)){
				f->eax = false;
				break;
			}

			dir = dir_open( inode_reopen(temp_inode) );
			
			set_pos( dir, file_tell( sys_file ) );
			bool answer = dir_readdir(dir, *(char**)(f->esp+8));
			file_seek( sys_file, get_pos( dir ) );
			
			dir_close(dir);
			f->eax = answer;
			break;

		// bool isdir(int fd)
		case SYS_ISDIR:
			check_uaddr( f->esp+4 );
			
			sys_file = get_file_from_thread( *(int*)(f->esp+4) );
			if(sys_file == NULL){
				f->eax = false;
				break;
			}

			temp_inode= file_get_inode( sys_file);
			
			f->eax = get_is_dir(temp_inode);
			break;

		// int inumber(int fd)
		case SYS_INUMBER:
			check_uaddr( f->esp+4 );
			
			sys_file = get_file_from_thread( *(int*)(f->esp+4) );
			if(sys_file == NULL){
				f->eax = false;
				break;
			}
			temp_inode= file_get_inode( sys_file);
			
			f->eax = inode_get_inumber(temp_inode);
			break;
#endif
	}	
	

//	hex_dump((uintptr_t)(PHYS_BASE-200),(void **)(PHYS_BASE-200),130,true);

	//  printf ("system call!\n");

	cur->syscall = false;
//  thread_exit ();
}


/* Reads a byte at user virtual address UADDR.
 *    UADDR must be below PHYS_BASE.
 *       Returns the byte value if successful, -1 if a segfault
 *          occurred. */
static int
get_user (uint8_t *uaddr)
{
	  int result;
//		printf(" %x %c ,", (unsigned int)(uaddr), (unsigned int)(*uaddr) );
	  asm ("movl $1f, %0; movzbl %1, %0; 1:"
				       : "=&a" (result) : "m" (*uaddr));
//		printf("%d  ->  ", result);
	  return result;
}
 
/* Writes BYTE to user address UDST.
 *    UDST must be below PHYS_BASE.
 *       Returns true if successful, false if a segfault occurred. */
static bool
put_user (uint8_t *udst, uint8_t byte)
{
	  int error_code;
	  asm ("movl $1f, %0; movb %b2, %1; 1:"
				       : "=&a" (error_code), "=m" (*udst) : "r" (byte));
	  return error_code != -1;
}

void
check_uaddr(uint8_t *uaddr){
//	printf("check_uaddr : %x %x\n",(unsigned int)uaddr+3, (unsigned int)PHYS_BASE);
	if(  (unsigned int)uaddr+3 >= (unsigned int) PHYS_BASE || get_user(uaddr+3) == -1){
		syscall_exit(-1);
	}
}

static void check_string_read(char **uaddr, int len){
//	printf("%x %x %c\n", (unsigned int)uaddr,(unsigned int) *uaddr,(unsigned int) *(*uaddr+2));
	int i, c;

	for(i=0;i<len;i++){
		if( (unsigned int)(*uaddr+i) >= (unsigned int)PHYS_BASE)
			syscall_exit(-1);
//		printf("( start!! %x, ", (unsigned int)(*uaddr+i) );
		if( (c = get_user((uint8_t *)(*uaddr+i)) ) == -1 )
			syscall_exit(-1);
		if( c=='\n' || c=='\0'){
			break;
		}
//		printf("end!!!!)     ");
	}
}

static void check_string_write(char** uaddr, int len){
	int i=0;
	for(i=0;i<len;i++){
		if( (unsigned int)(*uaddr+i) >= (unsigned int)PHYS_BASE)
			syscall_exit(-1);
		if( !put_user( (uint8_t *)(*uaddr+i),'\0' ) )
			syscall_exit(-1);
	}

}

